/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023.
 * Description: hkids common functions.
 * Author: weiyuchen
 * Create: 2023-09-14
 */

#include <linux/pid.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/printk.h>
#include <linux/jiffies.h>
#include <linux/pid_namespace.h>
#include <linux/task_struct_extend.h>
#include <linux/skbuff.h>
#include <linux/audit.h>
#include <linux/hkids/hkids.h>
#include <net/netlink.h>
#include <internal/hkids_internal.h>
#include <linux/rtos_boot_optimization.h>

#ifdef CONFIG_HKIDS_ENABLE_SWITCH
bool hkids_enable __ro_after_init;
#endif

#ifdef CONFIG_HKIDS_CHK_ABNORMAL_EXIT
void hkids_signal_deliver_trace(struct ksignal *ksig,
				struct task_struct *tsk)
{
	struct pid *spid;
	struct task_struct *sender;

	if (!trace_hkids_chkabexit_enabled() ||
	    ksig->info.si_code < 0)
		return;

	spid = find_get_pid(ksig->info.si_pid);
	if (!spid)
		return;

	sender = pid_task(spid, PIDTYPE_PID);
	if (!sender) {
		put_pid(spid);
		return;
	}

	if (RTOS_SEC_HKIDS_ENABLED)
		trace_hkids_chkabexit(ksig->info.si_signo,
				   sender, tsk);
	put_pid(spid);
	return;
}
#endif

#ifdef CONFIG_HKIDS_CHK_MLTDOWN
void task_clear_segv_count(struct task_struct *tsk)
{
	struct rtos_task_struct *rtos_task = task_to_rtos_task(tsk);
	if (rtos_task)
		rtos_task->segv_count = 0;
}
#endif

#ifdef CONFIG_HKIDS_CHK_KO_SIG
void hkids_report_abnormal_ko(long err)
{
	const char *err_msg;

	if (!RTOS_SEC_HKIDS_ENABLED)
		return;

	if (!is_hkids_chk_ko_sig_enabled())
		return;
	switch (err) {
	case -ENODATA:
		err_msg = "unsigned module";
		break;
	case -ENOPKG:
		err_msg = "module with unsupported crypto";
		break;
	case -ENOKEY:
		err_msg = "module with unavailable key";
		break;
	default:
		err_msg = "unknown error";
		break;
	}
#ifdef CONFIG_EULEROS_HKIDS
	pr_warn("comm=%s pid=%d errmsg=%s msg=chk_ko_sig key=hkids\n",
		current->comm, current->pid, err_msg);
#else
	hkids_report("comm=%s pid=%d errmsg=%s msg=chk_ko_sig key=hkids\n",
		current->comm, current->pid, err_msg);
#endif
}
#endif

#ifdef CONFIG_HKIDS_CHK_MAC_VIOLATION
static bool hkids_report_ratelimit(unsigned long *hkids_jiffies,
				unsigned int interval_msecs)
{
	unsigned long chk_interval = jiffies - *hkids_jiffies;

	if (*hkids_jiffies && chk_interval <=
		msecs_to_jiffies(interval_msecs))
		return false;

	*hkids_jiffies = jiffies;
	return true;
}

void hkids_chkmacatt_report(struct audit_buffer *ab,
	int result, u32 denied)
{
	static unsigned long chk_mac_violation_jiffies;
	unsigned int chk_interval;
	char *raw_data;
	char *data;

	if (!RTOS_SEC_HKIDS_ENABLED)
		return;
	chk_interval = get_chk_mac_interval();
	/* sad->result != 0: selinux log in enforce mode, 0: in permissive mode */
	if (result == 0 ||
		chk_interval == CHK_MAC_VIOLATION_DISABLED ||
		denied == 0)
		return;
	raw_data = audit_get_skbuff_data(ab);
	if (!raw_data)
		return;
	data = strstr(raw_data, "avc:");
	if (data && chk_interval == CHK_MAC_VIOLATION_NO_SPEED_LIMIT) {
#ifdef CONFIG_EULEROS_HKIDS
		pr_warn("%s msg=selinux key=hkids\n", data);
#else
		hkids_report("%s msg=selinux key=hkids\n", data);
#endif
		return;
	}
	if (data && hkids_report_ratelimit(&chk_mac_violation_jiffies, chk_interval))
#ifdef CONFIG_EULEROS_HKIDS
		pr_warn("%s msg=selinux key=hkids\n", data);
#else
		hkids_report("%s msg=selinux key=hkids\n", data);
#endif
}
#endif

#ifdef CONFIG_HKIDS_ENABLE_SWITCH
static int __init hkids_enable_setup(char *str)
{
	hkids_enable = true;
	return 1;
}
__setup("hkids_enable", hkids_enable_setup);

bool is_hkids_enabled(void)
{
	return !!hkids_enable;
}
#endif

#ifdef CONFIG_EULEROS_HKIDS
static DEFINE_SPINLOCK(hkids_control_lock);

struct hkids_control_ops *hkids_control;
void resigter_hkids_control(struct hkids_control_ops *co)
{
	spin_lock(&hkids_control_lock);
	hkids_control = co;
	spin_unlock(&hkids_control_lock);
}
EXPORT_SYMBOL(resigter_hkids_control);

void unresigter_hkids_control(struct hkids_control_ops *co)
{
	spin_lock(&hkids_control_lock);
	hkids_control = NULL;
	spin_unlock(&hkids_control_lock);
}
EXPORT_SYMBOL(unresigter_hkids_control);
#endif
